package implicits

import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
import play.api.libs.json._
import shapeless._
import shapeless.labelled.{FieldType, field}

/**
 * Created by Administrator on 2019/9/12
 */
trait MyJsonTool {

  trait MyCodec[A] {
    def encode(value: A): JsValue

    def decode(json: JsValue): A

    def dimap[B](enc: B => A, dec: A => B): MyCodec[B] =
      MyCodec.instance[B](enc andThen encode, dec compose decode)

  }

  trait ObjectCodec[A] extends MyCodec[A] {
    def encode(a: A): JsObject
  }

  object MyCodec {
    def apply[A](implicit codec: MyCodec[A]): MyCodec[A] = codec

    def instance[A](enc: A => JsValue, dec: JsValue => A): MyCodec[A] = new MyCodec[A] {
      def encode(a: A): JsValue = enc(a)

      def decode(json: JsValue): A = dec(json)
    }

    implicit val stringInstance: MyCodec[String] =
      instance(x => JsString(x), y => y.as[String])
    implicit val doubleInstance: MyCodec[Double] =
      instance(x => JsNumber(x), y => y.as[Double])
    implicit val intInstance: MyCodec[Int] =
      instance(x => JsNumber(x), y => y.as[Int])
    implicit val longInstance: MyCodec[Long] =
      instance(x => JsNumber(x), y => y.as[Long])
    implicit val booleanInstance: MyCodec[Boolean] =
      instance(x => JsBoolean(x), y => y.as[Boolean])
    implicit val mapInstance: MyCodec[Map[String, String]] =
      instance(x => Json.toJson(x), y => y.as[Map[String, String]])
    implicit val jsonInstance: MyCodec[JsValue] =
      instance(x => x, y => y)
    implicit val dateTimeInstance: MyCodec[DateTime] =
      instance(x => JsString(x.toString("yyyy-MM-dd HH:mm:ss")), y => {
        val formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss")
        formatter.parseDateTime(y.as[String])
      })

    implicit def listInstance[A]
    (implicit codec: MyCodec[A]): MyCodec[List[A]] =
      instance(x => JsArray(x.map(codec.encode)), y => y.asInstanceOf[JsArray].value.map(codec.decode).toList)

    implicit def optionInstance[A]
    (implicit codec: MyCodec[A]): MyCodec[Option[A]] =
      instance(opt => opt.map(codec.encode).getOrElse(JsNull), {
        case JsNull => None
        case j => Some(codec.decode(j))
      })
  }

  implicit object codecHNil extends ObjectCodec[HNil] {
    override def encode(a: HNil): JsObject = JsObject(Nil)

    override def decode(json: JsValue): HNil = HNil
  }

  implicit def codecHcons[K <: Symbol, V, T <: HList](implicit
                                                      key: Witness.Aux[K],
                                                      vc: Lazy[MyCodec[V]],
                                                      tc: ObjectCodec[T]) =
    new ObjectCodec[FieldType[K, V] :: T] {
      override def encode(a: FieldType[K, V] :: T): JsObject = {
        val fieldName: String = key.value.name
        val head = vc.value.encode(a.head)
        val tail = tc.encode(a.tail)
        JsObject((fieldName, head) +: tail.fields)
      }

      override def decode(json: JsValue): FieldType[K, V] :: T = {
        val name = key.value.name
        field[K](vc.value.decode(json.asInstanceOf[JsObject].value(name))) :: tc.decode(json)
      }
    }

  implicit def genericCodec[A, H](implicit gen: LabelledGeneric.Aux[A, H], hCodec: Lazy[ObjectCodec[H]]) =
    new ObjectCodec[A] {
      def encode(a: A) = {
        hCodec.value.encode(gen.to(a))
      }

      def decode(json: JsValue): A = gen.from(hCodec.value.decode(json))
    }

  implicit class MyJsonT[A](val t: A) {

    def myAsJson[H <: HList](implicit generic: LabelledGeneric.Aux[A, H],
                             hc: Lazy[ObjectCodec[H]]) = {
      hc.value.encode(generic.to(t))
    }
  }

  implicit class MyJsValue[T](jsValue: JsValue) {

    def toJsObject = {
      jsValue.as[JsObject]
    }

  }


}
